﻿using System;
using System.IO;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using ASquared;
using ASquared.SymbolicMath;
using ASquared.ModelOptimization;
using Csu.Modsim.ModsimModel;

namespace iwpm.optimization
{
    public enum TestOption { HydroScenario, GrandCouleeTDG, GrandCouleeTestBus, GrandCouleeTestBusWind, Route, Undefined }
    public class Tester
    {

        private string _file, _basename, _ext;
        private int _nproc, _listCnt;
        private IScenarioTestable _scens, _currlist;
        private object _mylock = new object();
        private List<Process> _procList = new List<Process>();
        private TestOption _opt;

        public Tester(string file, int NumOfProcesses,
            TestOption opt)
        {
            _file = file;
            _basename = Path.GetDirectoryName(Path.GetFullPath(file)) + "\\" + Path.GetFileNameWithoutExtension(file);
            _ext = Path.GetExtension(file);
            _nproc = NumOfProcesses;
            _opt = opt;
            _scens = GetList(_file);
        }

        public void Run()
        {
            if (_nproc > 1)
                RunMultipleProcesses();
            else
                RunScenarios(_scens);
        }

        private IScenarioTestable GetList(string file)
        {
            if (!File.Exists(file))
            {
                switch (_opt) 
                {
                    case TestOption.HydroScenario: 
                        return new HydroScenarioList();
                    case TestOption.GrandCouleeTDG:
                        return new GrandCouleeTDGList();
                    case TestOption.GrandCouleeTestBus: 
                        return new GrandCouleeTestBusList();
                    case TestOption.GrandCouleeTestBusWind: 
                        return new GrandCouleeTestBusWindList(); 
                    default:
                        throw new Exception("Option " + _opt.ToString() + " is not implemented in code.");
                }
            }
            else
            {
                switch (_opt)
                {
                    case TestOption.HydroScenario:
                        return HydroScenarioList.FromFile(file);
                    case TestOption.GrandCouleeTDG:
                        return GrandCouleeTDGList.FromFile(file);
                    case TestOption.GrandCouleeTestBus:
                        return GrandCouleeTestBusList.FromFile(file);
                    case TestOption.GrandCouleeTestBusWind:
                        return GrandCouleeTestBusWindList.FromFile(file);
                    default:
                        throw new Exception("Option " + _opt.ToString() + " is not implemented in code.");
                }
            }
        }

        private void RunMultipleProcesses()
        {
            if (_nproc > _scens.Count)
                _nproc = _scens.Count;
            int nPerProc = _scens.Count / _nproc;
            _listCnt = 1;
            _currlist = GetList("");
            for (int i = 0; i < _scens.Count; i++)
            {
                _currlist.Add(_scens[i]);
                if (((i + 1) % nPerProc == 0 && _listCnt != _nproc) || i == _scens.Count - 1)
                {
                    RunOnNewProcess();
                    _currlist = GetList("");
                    _listCnt++;
                }
            }
        }

        private void RunOnNewProcess()
        {
            string newfile;
            Console.WriteLine("Spawning new process to handle " + _currlist.Count + " simulations.");
            newfile = _basename + "_" + _listCnt + _ext;
            _currlist.WriteToFile(newfile);

            // start a new process and redirect output to a text file.
            Process currProc = new Process();
            currProc.StartInfo.UseShellExecute = false;
            currProc.StartInfo.RedirectStandardOutput = true;
            currProc.StartInfo.FileName = typeof(Tester).Assembly.Location;
            currProc.StartInfo.Arguments = "\"" + newfile + "\"";
            currProc.Start(); 
            // Process.Start("\"" + typeof(Tester).Assembly.Location + "\"", "\"" + newfile + "\"");

            // we want to know when this process exited.
            currProc.Exited += new EventHandler(currProc_Exited);
            _procList.Add(currProc);

            // write the console output in a text file using a separate thread. 
            string consout = _basename + "_" + _listCnt + "_console.txt";
            Console.WriteLine("Writing console output to text file: " + consout); 
            Thread t = new Thread(delegate() { WriteConsoleOutput(currProc, consout); });
            t.Start();
        }

        private void WriteConsoleOutput(Process p, string fileloc)
        {
            StreamWriter sw = new StreamWriter(fileloc);
            while (!p.StandardOutput.EndOfStream)
                sw.WriteLine(p.StandardOutput.ReadLine());
            sw.Close();
        }

        void currProc_Exited(object sender, EventArgs e)
        {
            Process proc = ((Process)sender);
            Console.WriteLine("Process " + proc.Id + " has finished with code " + proc.ExitCode);
        }

        public void RunScenarios(IScenarioTestable list)
        {
            try
            {
                //Sym.MaximumFittedPolynomialOrder = 20;
                //HydropowerController.PolynomialTargetRsquared = 0.9995;
                //HydropowerController.UsePolynomialHeadFxn = true;
                list.SolveAll();
                if (list.ErrorOccurred)
                {
                    Console.WriteLine("\n\nA non-critical simulation error occurred... Check outputs to ensure reasonable results.");
#if DEBUG
                    Console.WriteLine("\nPress any key to continue...");
                    Console.ReadKey();
#endif
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("\n\nError occurred while running... \n" + ex.ToString());
#if DEBUG
                Console.WriteLine("\nPress any key to continue...");
                Console.ReadKey();
#endif
            }
        }

        public static void Main(string[] args)
        {
            string file = "";
            int nproc = 0;
            if (args.Length != 1 || !File.Exists(args[0]))
            {
                if (args.Length > 1)
                {
                    file = args[0];
                    Int32.TryParse(args[1], out nproc);
                }

                while (!File.Exists(file))
                {
                    Console.WriteLine("Please specify an existing scenarios file path (q-quit): ");
                    file = Console.ReadLine();
                    if (file.ToLower().Equals("q"))
                        return;
                }
            }

            // Run the tests...
            file = args[0];
            TestOption opt = TestOption.Undefined;
            if (!file.EndsWith(".xy"))
            {
                opt = getOption(file);
                if (opt == TestOption.Undefined)
                {
                    Console.WriteLine("The file needs to have one of the following extensions:\n\t"
                        + string.Join("\n\t", Extensions));
                    Console.WriteLine("\nPress any key to exit...");
                    Console.ReadKey();
                    return;
                }
                Console.WriteLine("Reading scenarios list.");
                Tester tester = new Tester(file, nproc, opt);
                Console.WriteLine("\n\nRunning the networks...\n\n");
                //HydropowerController.ElevType = ElevationFunctionType.Polynomial;
                tester.Run();
            }
            else
            {
                opt = TestOption.Route;
                Routing router = new Routing(file, args[1]);
                router.run(); 
            }
        }

        public readonly static string[] Extensions = new string[] { ".hscen", ".gctdg", ".gc2bus", ".gcwind", ".xy" };

        private static TestOption getOption(string file)
        {
            string ext = Path.GetExtension(file);
            for (int i = 0; i < Extensions.Length; i++)
                if (ext.Equals(Extensions[i]))
                    return (TestOption)i;
            return TestOption.Undefined; 
        }

        //// Main 
        //static void Main(string[] args)
        //{
        //    if (args.Length != 1)
        //    {
        //        Console.WriteLine("Arguments: xyfilepath");
        //        return;
        //    }

        //    HydropowerTarget(args[0]); 
        //}

        //// Hydropower target 
        //public static void HydropowerTarget(string filepath)
        //{
        //    string dir = Path.GetDirectoryName(Path.GetFullPath(System.Reflection.Assembly.GetExecutingAssembly().Location)) + @"\";
        //    StreamWriter sw = new StreamWriter(dir + "log.txt");

        //    Console.WriteLine("Conditional Gradient Method");

        //    string outline = "Tolerance\tMax iters\tx\ty\tz\tTime";
        //    Console.WriteLine(outline);
        //    sw.WriteLine(outline);

        //    Stopwatch s = new Stopwatch();

        //    // set the weight 
        //    double weight = 1e8;
        //    Console.WriteLine("\nWeight = " + weight);
        //    sw.WriteLine("\nWeight = " + weight);

        //    // instantiate the model and solver 
        //    OptiModel optModel = new OptiModel(filepath, ObjectiveFormulation.HydroTargets, weight);
        //    optModel.MODSIMModel.hydro.IsActive = false;
        //    optModel.Debug(Path.GetDirectoryName(Path.GetFullPath(filepath)) + @"\debug_HydroTargetSeeker.txt"); 
        //    ConditionalGradientSolver solver = new ConditionalGradientSolver(optModel, false); //true);
        //    optModel.MODSIMModel.OnModsimError += new Csu.Modsim.ModsimModel.Model.OnModsimErrorDelegate(OnModsimMessage);

        //    // Set solution parameters
        //    solver.WriteLog = true;
        //    solver.Tolerance = 1; // 1 / optModel.MODSIMModel.ScaleFactor;
        //    int maxIters = 1000;
        //    solver.MaxIterations = maxIters;
        //    optModel.MODSIMModel.maxit = maxIters;
        //    try
        //    {
        //        s.Reset();
        //        s.Start();
        //        solver.Solve();
        //        s.Stop();
        //        outline = solver.Tolerance.ToString() + "\t" +
        //            solver.Iteration + "\t" +
        //            solver.Optimum + "\t" +
        //            s.Elapsed.TotalMilliseconds;
        //        Console.WriteLine(outline);
        //        sw.WriteLine(outline);
        //    }
        //    catch (Exception ex)
        //    {
        //        Console.WriteLine("Error:\n" + ex.ToString());
        //        Console.WriteLine("\nPress any key to continue.");
        //        Console.ReadKey();
        //        return;
        //    }
        //    sw.Close();
        //    Console.WriteLine("\nDone");
        //    Console.WriteLine("\nPress any key to continue...");
        //    Console.ReadKey();
        //}
        //private static void OnModsimMessage(string msg)
        //{
        //    Console.WriteLine(msg);
        //}
    }
}
